package logic;

import eduni.simjava.Sim_entity;
import eduni.simjava.Sim_event;
import gridsim.GridSim;
import gridsim.GridSimCore;
import gridsim.GridSimTags;
import gridsim.Machine;
import gridsim.MachineList;
import gridsim.PE;
import gridsim.PEList;
import gridsim.ParameterException;
import gridsim.ResourceCharacteristics;
import gridsim.net.FIFOScheduler;
import gridsim.net.RIPRouter;
import gridsim.net.SimpleLink;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;
import logic.gridSimInterface.SimTags;
import logicTests.Dbg;
import logic.SimulationRunState;
import logic.topology.*;
import logic.gridSimInterface.brokers.*;
import logic.gridSimInterface.resource.*;
import logic.gridSimInterface.user.*;
import logic.gridNodes.*;


/**
 * Hlavna trieda simulacie. Ma na starosti spustenie simulacie.
 * Udrzuje si informaciu o stave simulacie. Po ukonceni simulacie
 * obsahuje vysledne statistiky.
 */
public class JSimulation {
    private static SimulationRunState state;
    private static String hermesName = "__hermes__";
    private static Hermes _hermes = null;
    
    /**
     * ID globalneho brokera - potrebne pre spustenie simulacie
     */
    private static int globalBrokerID = 0;

    private static int resourcesCount = 0;
    private static int routersCount = 0;
    private static int usersCount = 0;
    private static int brokersCount = 0;
    private static int globBrokersCount = 0;
    private static int tasksCount = 0;
    private static GridBrokerGlobal globalBroker = null;

    private static LinkedList<Statistic> statistics;
    
    private static HashMap<String, Sim_entity> entities = null;
    
    private static boolean trace_flag = false;

    private static boolean forcedStop;
    
    /**
     * Vytvorenie simulacnych entit podla GridTopology. Inicializacia
     * balika GridSim. Spustenie
     * simulacie s parametrami SimulationParameters.
     * @param simulationState   premenna, do ktorej sa budu ukladat
     *                          zmeny stavu simulacie
     * @throws Exception         Ak checkTopology() nevrati null.
     */
    public static void start(SimulationRunState simulationState) throws Exception {
        LinkedList<String> topologyOK = checkTopology();
        if (topologyOK != null)
            throw new Exception(topologyOK.getFirst());
        
        forcedStop = false;
        state = simulationState;
        if (statistics != null) {
            statistics.clear();
            statistics = null;
        }

        Dbg.vypis("JSimulation.start() begin ...");
        state.setState(SimulationRunState.STATE_INIT);
        state.setTasksCount(tasksCount);
        countEntities();
//        SimulationParameters.set

        GridSim.init(usersCount, Calendar.getInstance(), trace_flag);
        
        freeEntities();
        createEntities();
        simulate();
        Dbg.vypis("JSimulation.start() end");
        
    }

    /**
     * @return aktualny stav simulacie
     */
    public static SimulationRunState getState() {
        return state;
    }

    /**
     * Pridanie novej statistiky do zoznamu statistik.
     * @param newStatistic  nova vysledna statistika. Musi mat vyplenu
     *                      aspon priemernu hodnotu.
     */
    public static void addStatistic(Statistic newStatistic) {
        statistics.add(newStatistic);
    }
    
    /**
     * Spustenie samotnej simulacie. Vytvorenie pomocnej entity Hermes.
     * Po ukonceni simulacie zavola freeEntities().
     */
    private static void simulate() {
        Dbg.vypis("JSimulation.simulate() begin...");
        System.out.println("simulate() - getUserCount() " + getUsersCount());
        state.setState(SimulationRunState.STATE_RUNNING);
        
        try {
            _hermes = new Hermes();
            GridSim.startGridSimulation();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        if (!isForcedStop()) {
            state.setState(SimulationRunState.STATE_FINISHING);
        }
        freeEntities();
        if (!isForcedStop()) {
            state.setState(SimulationRunState.STATE_FINISHED);
        } else {
            state.setState(SimulationRunState.STATE_STOPPED);
        }
        Dbg.vypis("JSimulation.simulate()");
    }

    /**
     * @return zoznam vsetkych vyslednych statistik.
     */
    public static LinkedList<Statistic> getStatistics() {
        return statistics;
    }
    
//    public static void vypisStat() {
  //      Dbg.vypis("Statistics:");
    //    for(Statistic st : statistics)
      //      Dbg.vypis(st.getEntityName() + " " + st.getType() + " " + st.getMeanValue() + 
//                " " + st.getStandardDeviation());
  //  }
    
    /**
     * Kontrola pripustnosti topologie. Pripustna topologia musi
     * obsahovat prave jedneho globalneho brokera, aspon jedneho
     * lokalneho brokera, aspon jedneho uzivatela a aspon jeden zdroj.
     * Uzly mozu byt navzajom pospajane iba tak, ako je to v ich popise.
     * Vsetky uzly musia mat aspon jedno spojnie. Medzi lokalnymi
     * brokermi nemoze existovat ina cesta ako cez globalne routre.
     * @return Null, ak je topologia pripustna. Inak
     * vracia zoznam chyb topologie.
     */
    public static LinkedList<String> checkTopology() {
        LinkedList<String> result = new LinkedList<String>();
        countEntities();
        
        if (globBrokersCount == 0)
            result.add("There is no global broker!");
        if (globBrokersCount > 1)
            result.add("Only one global broker is allowed!");
        if (resourcesCount == 0)
            result.add("There are no resources!");
        if (usersCount == 0)
            result.add("There are no users!");
        if (brokersCount == 0)
            result.add("There  are no brokers!");
        
        if (resourcesCount + routersCount + usersCount + brokersCount + globBrokersCount
                != GridTopology.getNodes().size())
            result.add("There exists unknown type of entity!");

        Collection<NetworkLink> linky = null;
        NetworkNode druhy = null;
        for (NetworkNode uzol : GridTopology.getNodes()) {
            linky = uzol.getLinks();
            if ((linky == null) || (linky.size() == 0)) {
                result.add("Node " + uzol.getName() + " is not connected to anything!");
                continue;
            }
            
            if (uzol instanceof GridRouterGlobal) {
                for (NetworkLink l : linky) {
                    druhy = l.getAnotherEnd(uzol);
                    if (!(
                            (uzol instanceof GridRouterGlobal)
                            || (uzol instanceof GridBrokerGlobal)
                            || (uzol instanceof GridUser)
                            || (uzol instanceof GridBroker)
                          )
                        )
                        result.add("Router " + uzol.getName() + " connected to" +
                                " not allowed node - " + druhy.getName() + "!");
                }
            } else if (uzol instanceof GridRouterLocal) {
                for (NetworkLink l : linky) {
                    druhy = l.getAnotherEnd(uzol);
                    if (!(
                            (uzol instanceof GridResource)
                            || (uzol instanceof GridRouterLocal)
                            || (uzol instanceof GridUser)
                            || (uzol instanceof GridBroker)
                          )
                        )
                        result.add("Router " + uzol.getName() + " connected to" +
                                " not allowed node - " + druhy.getName() + "!");
                }
            } else if (uzol instanceof GridResource) {
                if (linky.size() > 1)
                    result.add("Resource + " + uzol.getName() + " has too much connections!");
                if (!(linky.iterator().next().getAnotherEnd(uzol) instanceof GridRouterLocal))
                    result.add("Resource " + uzol.getName() + " is not connected to router!");
            } else if (uzol instanceof GridUser) {
                if (linky.size() > 1)
                    result.add("User + " + uzol.getName() + " has too much connections!");
                druhy = linky.iterator().next().getAnotherEnd(uzol);
                if (! ((druhy instanceof GridRouterLocal) || (druhy instanceof GridRouterGlobal)))
                    result.add("User " + uzol.getName() + " is not connected to router!");
            } else if (uzol instanceof GridBroker) {
                druhy = linky.iterator().next().getAnotherEnd(uzol);
                if (!(druhy instanceof GridRouterGlobal) &&
                    !(druhy instanceof GridRouterLocal)) {
                    result.add("Broker " + uzol.getName() + " is connected to" +
                            " not allowed node - " + druhy.getName() + "!");
                    continue;
                }
                Iterator<NetworkLink> it = linky.iterator();
                it.next();
                if (!it.hasNext()) {
                    result.add("Broker " + uzol.getName() + " has not enough connections!");
                    continue;
                }
                NetworkNode treti = it.next().getAnotherEnd(uzol);
                if (!(treti instanceof GridRouterGlobal) &&
                    !(treti instanceof GridRouterLocal)) {
                    result.add("Broker " + uzol.getName() + " is connected to" +
                            " not allowed node - " + treti.getName() + "!");
                    continue;
                }
                if (treti.getClass().equals(druhy.getClass())) {
                    result.add("Broker " + uzol.getName() + " must be connected " +
                            "to one global and one local router!");
                    continue;
                }
                String druhyBroker = ((GridBroker)uzol).getConnectedBroker();
                if (druhyBroker != null)
                    result.add("Broker " + uzol.getName() + " has shared resources" +
                            " with broker " + druhyBroker + "!");
                
            } else if (uzol instanceof GridBrokerGlobal) {
                if (linky.size() > 1)
                    result.add("Global broker + " + uzol.getName() + " has too much connections!");
                if (!(linky.iterator().next().getAnotherEnd(uzol) instanceof GridRouterGlobal))
                    result.add("Global broker " + uzol.getName() + " is not connected to global router!");
            }
        }
        
        
        
        if (result.size() == 0)
            return null;
        else
            return result;
    }
    
    /**
     * Vyprazdnenie premennych obsahujucich informacie o
     * simulacnych entitach.
     */
    private static void freeEntities() {
        Dbg.vypis("freeEntities()");
        if (entities != null) {
            entities.clear();
            entities = null;
        }
        
        int globalBrokerID = 0;

        int resourcesCount = 0;
        int routersCount = 0;
        int usersCount = 0;
        int brokersCount = 0;
        int globBrokersCount = 0;
        GridBrokerGlobal globalBroker = null;
}

    /**
     * Spocitanie vsetkych entit do atributov. Nastavenie globalBroker
     * na prislusnu entitu.
     */
    private static void countEntities() {
        routersCount = 0;
        resourcesCount = 0;
        usersCount = 0;
        tasksCount = 0;
        brokersCount = 0;
        globBrokersCount = 0;
        globalBroker = null;
        
        Collection<NetworkNode> uzly = (Collection<NetworkNode>) GridTopology.getNodes();
        for (NetworkNode uzol : uzly) {
            if ((uzol instanceof GridRouterGlobal) ||
                (uzol instanceof GridRouterLocal)) {
                routersCount++;
            } else if (uzol instanceof GridResource) {
                resourcesCount++;
            } else if (uzol instanceof GridUser) {
                usersCount++;
                tasksCount+= ((GridUser)uzol).getTasksCount();
            } else if (uzol instanceof GridBroker) {
                brokersCount++;
            } else if (uzol instanceof GridBrokerGlobal) {
                globBrokersCount++;
                globalBroker = (GridBrokerGlobal)uzol;
            }
        }
    }
    
    /**
     * Vytvorenie simulacnych entit a ich spojeni.
     * Lokalny broker sa nahradi dvojicou router a broker, aby bolo
     * mozne mat z neho dve spojenia.
     * Naplnenie hashovacej tabulky pre pristup k entitam podla mena.
     * Zaregistrovanie lokalnych brokerov u globalneho brokera.
     * Zaragistrovanie zdrojov ku prisluchajucim lokalnym brokerom.
     */
    private static void createEntities() throws Exception {
        Dbg.vypis("createEntities()");
        countEntities();
        
        statistics = new LinkedList<Statistic>();
        
        Collection<NetworkNode> nodes = (Collection<NetworkNode>) GridTopology.getNodes();
        Collection<NetworkLink> links = (Collection<NetworkLink>) GridTopology.getLinks();
        
        BrokerHierarchical globalBrokerEntity = globalBroker.createBrokerGlobal();
        globalBrokerID = globalBrokerEntity.get_id();

        
        entities = new HashMap<String, Sim_entity>(
                resourcesCount + usersCount + 2*brokersCount + 1 + routersCount);
        entities.put(globalBroker.getName(), globalBrokerEntity);

        //vytvorenie entit, ich zaradenie do hashovacej tabulky
        //lokalne brokre sa registruju u globalneho brokera
        for (NetworkNode uzol : nodes) {
            LinkedList<Sim_entity> newEntities = uzol.createGridSimEntities(
                    getGlobalBroker(),trace_flag);

            for (Sim_entity entity : newEntities) {
                entities.put(entity.get_name(), entity);
                System.out.println("Adding " + entity.get_name());
            }

            if (uzol instanceof GridBroker) {
                globalBrokerEntity.addSubBroker(uzol.getName());
            }
        }
        
        Dbg.vypis("createEntities() - vytvorene vsetky entity");
        
        for (NetworkLink link : links) {
            //uzol linky, ktory je router
            NetworkNode routerNode = null;
            //uzol linky, ktory nemusi byt router
            NetworkNode koniec = null;
            if ((link.getBegin() instanceof GridRouterGlobal) ||
                (link.getBegin() instanceof GridRouterLocal)) {
                routerNode = link.getBegin();
                koniec = link.getEnd();
            } else {
                routerNode = link.getEnd();
                koniec = link.getBegin();
            }
            
            RIPRouter router = (RIPRouter) entities.get(routerNode.getName());
            FIFOScheduler linkScheduler = new FIFOScheduler(
                    "FIFOScheduler_" + link.getName());
            
            if ((koniec instanceof GridBroker) || 
                (koniec instanceof GridRouterGlobal) ||
                (koniec instanceof GridRouterLocal)) {
                //napojenie na iny router
                
                String druhyName = null;
                if (koniec instanceof GridBroker)
                    druhyName = ((GridBroker)koniec).brokerRouterName();
                else
                    druhyName = koniec.getName();
                
                RIPRouter druhyRouter = (RIPRouter) entities.get(druhyName);
                FIFOScheduler druhyScheduler = new FIFOScheduler(
                        "FIFOScheduler_" + link.getName() + "_router_"
                        + druhyRouter.getName());
                
                router.attachRouter(druhyRouter, link.createSimpleLink(), 
                        linkScheduler, druhyScheduler);
                
                Dbg.vypis("attachRouter " + routerNode.getName() + " + " + druhyName);
            } else {
                //napojenie na usera, resource
                GridSimCore entita = (GridSimCore) entities.get(koniec.getName());
                router.attachHost(entita, linkScheduler);
                
                Dbg.vypis("attachHost " + routerNode.getName() + " + " + koniec.getName());
            }
        }
        
        Dbg.vypis("createEntities() - vytvorene vsetky prepojenia");
        
        for (NetworkNode uzol : nodes) 
            if (uzol instanceof GridBroker) {
                attachBrokerResourcesID((GridBroker)uzol);
            }
        
        Dbg.vypis("createEntities() - stop");
    }
    

    /**
     * Brokerovi priradi podla topologie vsetky zdroje, ktore mu patria.
     * Zdroje sa priradzuju vytvorenemu BrokerLocal s rovnakym menom.
     * Metoda sa moze volat, az ked su vytvorene vsetky entity.
     */
    private static void attachBrokerResourcesID(GridBroker gb) {
        BrokerLocal b = (BrokerLocal) entities.get(gb.getName());
        
        Vector<NetworkNode> localResources = gb.getLocalResources();
        String resources = "";
        for (NetworkNode res : localResources) {
            GridSimCore resEntity = (GridSimCore) entities.get(res.getName());
            b.addResource(resEntity.get_id());
            resources = resources + " " + resEntity.get_name();
        }
        Dbg.vypis("Attached resources " + resources + " to " + gb.getName());
    }

    public static String getHermesName() {
        return new String(hermesName);
    }
    
    public static String getGlobalBroker() {
        return globalBroker.getName();
    }
    
    public static int getUsersCount() {
        return usersCount;
    }
    
////////////////////////////////////////////////////////////////////////////////    
/////////////////////////////// HERMES /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
    
    /**
     * Pomocna entita, ktora na zaciatku posle globalnemu brokerovi
     * SIMULATION_START. Potom caka na jeho odpoved BROKER_READY.
     * Nasledne rozposle vsetkym uzivatelom SIMULATION_START.
     * Caka kym od vsetkych uzivatelom nepride sprava NO_MORE_GRIDLETS.
     * Rozposle vsetkym entitam END_OF_SIMULATION. Zavola
     * shutdownUserEntity() a shutdownGridStatisticsEntity().
     */
    private static class Hermes extends GridSim {
        public Hermes() throws Exception {
            super(getHermesName(),new SimpleLink(
                                    "__hermes__link",
                                    999999,
                                    1,
                                    1000000
                               )
                 );
        }
        
        public void body() {
            super.gridSimHold(1.0);   
            Sim_event ev = new Sim_event();

            int planning = SimulationParameters.getPlanningType();
            
            send(globalBrokerID,  0,SimTags.SIMULATION_START, new Integer(planning));

            while (true) {
                sim_get_next(ev);
                if(ev.get_tag() == SimTags.BROKER_READY) {
                    System.out.println("Entity " + this.get_name() + 
                            " recieved broker ready at time " + GridSim.clock());

                    break;
                }
            }    	

            for (Sim_entity entity : entities.values()) {
                if (entity instanceof UserSimple) {
                    UserSimple u = (UserSimple) entity;
                    super.send(super.getEntityId(
                            u.get_name()), 
                            GridSimTags.SCHEDULE_NOW, 
                            SimTags.SIMULATION_START,
                            new Integer(planning)
                            );
                }
            }

            int gridletOwners = usersCount;
            for ( sim_get_next(ev); ev.get_tag() != GridSimTags.END_OF_SIMULATION;
            sim_get_next(ev))
            {
                            if(ev.get_tag() == SimTags.NO_MORE_GRIDLETS)
                                    gridletOwners--;
                            if(gridletOwners <= 0)
                                    break;        	
            }    	

            if (!isForcedStop())
                state.setState(SimulationRunState.STATE_FINISHING);
            
            //Signals the end of simulation to all entities
            for (Sim_entity entity : entities.values()) {
                send(entity.get_name(), GridSimTags.SCHEDULE_NOW, GridSimTags.END_OF_SIMULATION);
                Dbg.vypis("Poslane END_OF_SIMULATION " + entity.get_name());
            }
            
            super.shutdownGridStatisticsEntity();
            super.shutdownUserEntity();
            super.terminateIOEntities();
        }
    }

    public static boolean isForcedStop() {
        return forcedStop;
    }
    
}
